Introduction

pk <- c('tidyverse', 'sf', 'ggrepel', 'fingertipsR', 'ggmap')
sapply(pk, function(x){
  if(!(x %in% installed.packages())){
    install.packages(x, character.only = TRUE)
  }
  require(x, character.only = TRUE)
})
Loading required package: tidyverse
── Attaching packages ──────────────────────────────────────────────────────────────────────────────────────── tidyverse 1.2.1 ──
✔ ggplot2 3.2.1     ✔ purrr   0.3.2
✔ tibble  2.1.3     ✔ dplyr   0.8.3
✔ tidyr   1.0.0     ✔ stringr 1.4.0
✔ readr   1.3.1     ✔ forcats 0.4.0
── Conflicts ─────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter()           masks stats::filter()
✖ dplyr::lag()              masks stats::lag()
✖ .GlobalEnv::theme_dark()  masks ggplot2::theme_dark()
✖ .GlobalEnv::theme_light() masks ggplot2::theme_light()
Loading required package: sf
Linking to GEOS 3.7.2, GDAL 2.4.2, PROJ 5.2.0
Loading required package: ggrepel
Loading required package: fingertipsR
Registered S3 methods overwritten by 'htmltools':
  method               from         
  print.html           tools:rstudio
  print.shiny.tag      tools:rstudio
  print.shiny.tag.list tools:rstudio
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio
Loading required package: ggmap
Google's Terms of Service: https://cloud.google.com/maps-platform/terms/.
Please cite ggmap if you use it! See citation("ggmap") for details.
  tidyverse          sf     ggrepel fingertipsR       ggmap 
       TRUE        TRUE        TRUE        TRUE        TRUE 
list.files('etc', full.names = TRUE, pattern = '.r') %>%
  map(source)
[[1]]
[[1]]$value
function(base_family = 'Helvetica Neue Thin', base_size = 10){
    theme_minimal(base_family = base_family, base_size = base_size) %+%
      theme(
        plot.background = element_rect(fill = colours[['dark']], colour = 'transparent'),
        panel.grid = element_blank(),
        plot.title = element_text(size = rel(1.5), margin = margin(10,2,10,2)),
        text = element_text(colour = colours[['light']]),
        axis.text = element_text(colour = colours[['light']])
      )
  }

[[1]]$visible
[1] FALSE
if(!dir.exists('map_json')){dir.create('map_json')}

load_sf <- function(u, path, file){
  fp <- paste0(path, '/', file, '.geojson')
  if(!file.exists(fp)){
    download.file(url = u, destfile = fp)
  }
  read_sf(fp)
}
  
ics <- 'https://opendata.arcgis.com/datasets/1a3b3e9f044a44a895d64000cddf9272_0.geojson' %>%
  load_sf(path = 'map_json', file = 'ics') %>%
  mutate(
    stp_name = 
      str_remove_all(string = str_to_lower(stp19nm),
                              pattern = 'health.*care.*|partners in|health[a-z]{0,3}|join.*care|our|\\(') %>%
      str_squish() %>%
      str_to_title() %>%
      str_replace_all(c('And'='and', 'The' = 'the', 'Of' = 'of'))
  )

'https://opendata.arcgis.com/datasets/c661a8377e2647b0bae68c4911df868b_3.geojson'
[1] "https://opendata.arcgis.com/datasets/c661a8377e2647b0bae68c4911df868b_3.geojson"
msoa <- 'https://opendata.arcgis.com/datasets/c661a8377e2647b0bae68c4911df868b_3.geojson' %>%
  load_sf(path = 'map_json', file = 'msoa')

eng <- 'https://opendata.arcgis.com/datasets/629c303e07ee4ad09a4dfd0bfea499ec_0.geojson' %>%
  load_sf(path = 'map_json', file = 'countries') %>% 
  filter(ctry18nm == 'England')

eng_punch <-
  eng %>%
  st_buffer(dist = .2) %>%
  st_bbox() %>% 
  st_make_grid(n = 1) %>% 
  st_difference(eng)
dist is assumed to be in decimal degrees (arc_degrees).
although coordinates are longitude/latitude, st_difference assumes that they are planar

Plotting ICSs

Lining things up neatly is always a plus. Using a fig aspect rtio of 1.2535 gets rid of the strips around the size for this projection.

m <-
  ggplot(ics, aes(x = long, y = lat, label = str_wrap(stp19nm, 25), fill = st_areashape)) +
  geom_sf(size = .1, colour = colours[['dark']],  alpha = .5, fill = colours[['light']]) +
  scale_fill_viridis_c() +
  labs(
    title = 'A simple ICS map'
  ) +
  theme_dark() +
  theme(
    legend.position = c(.1, .5),
    axis.text = element_blank(),
    axis.title = element_blank()
  )

if(!dir.exists('output/maps')){dir.create('output/maps', recursive = TRUE)}
ggsave(plot = m, path = 'output/maps', filename = 'simple_ics.png', height = 8, width = 8/1.25, bg = '#222629')

m

Getting some data

Handily the fingetipsR package from Public Health England has some great data at MSOA level which works really nicely on maps to illustrate health inequalities. Download a local copy of the file to save hammering the PHE servers! This will take a while the first time it’s run because it needs to fetch data and write it locally but should speed up after the downloads from PHE are done.

if(!dir.exists('data')){dir.create('data')}

# get fingertips data
if(!file.exists('data/fingertips_data.csv')){
  fingertipsR::indicators() %>%
  filter(str_detect(tolower(IndicatorName), 'healthy life exp|mortality')) %>%
  pluck('IndicatorID') %>%
  unique() %>%
  fingertips_data(AreaTypeID = 3) %>%
  write.csv('data/fingertips_data.csv', na = '')
}

# get lsoa lookup
if(!file.exists('data/lsoa_stp.csv')){
  'https://opendata.arcgis.com/datasets/520e9cd294c84dfaaf97cc91494237ac_0.csv' %>%
    download.file('data/lsoa_stp.csv', mode = 'w')
}

# get oa lookup
if(!file.exists('data/oa_lookup.csv')){
  'http://geoportal1-ons.opendata.arcgis.com/datasets/fe6c55f0924b4734adf1cf7104a0173e_0.csv' %>%
    download.file('data/oa_lookup.csv', mode = 'w')
}

data <- read_csv('data/fingertips_data.csv')
lsoa_stp <-read_csv('data/lsoa_stp.csv')
oa_lookup <-
  read_csv('data/oa_lookup.csv') %>%
  group_by(LSOA11CD, LSOA11NM, MSOA11CD, MSOA11NM) %>%
  summarise() %>%
  left_join(lsoa_stp, by = 'LSOA11CD') %>%
  group_by(MSOA11CD, MSOA11NM, LAD19CD, LAD19NM, CCG19CD, CCG19NM, STP19CD, STP19NM) %>%
  summarise() %>%
  ungroup()
msoa_data <-
  data %>%
  as_tibble() %>%
  filter(AreaType == "MSOA") %>%
  transmute(
    ind_id = IndicatorID,
    ind_name = IndicatorName,
    msoa_code = AreaCode,
    value = Value,
    count = Count,
    dnm = Denominator
  ) %>%
  left_join(y = 
              oa_lookup %>%
              transmute(
                msoa_code = MSOA11CD,
                lad_code = LAD19CD,
                lad_name = LAD19NM,
                ccg_code = CCG19CD,
                ccg_name = CCG19NM,
                stp_code = STP19CD,
                stp_name = STP19NM
              ),
            by = 'msoa_code'
            ) %>%
  left_join(y = 
              msoa %>%
              transmute(
                objectidid = objectid,
                msoa_code = msoa11cd,
                msoa_name = msoa11nm,
                geometry = geometry
              ),
            by = 'msoa_code'
  ) %>%
  st_as_sf()

# clean up
need <- c('msoa_data', 'ics', 'eng', 'eng_punch', 'theme_dark', 'theme_light', 'colours')
rmv <- c('rmv', ls()[!(ls() %in% need)])
rm(list = rmv)
msoa_map <- 
  msoa_data %>% 
  filter(ind_name =="Healthy life expectancy, (upper age band 85+)") %>%
  ggplot(aes(fill = value)) +
  geom_sf(colour = 'transparent', size = 0, alpha =.7) +
  geom_sf(data = ics, colour = colours[['light']], size = .25, fill = 'transparent', inherit.aes = FALSE) +
  geom_text(data = ics, 
            aes(x = long, y = lat, label = stp_name %>% str_wrap(25)), 
            size = 1.5, colour = colours[['light']], inherit.aes = FALSE) + 
  scale_fill_viridis_c(option = 'D', direction = -1) +
  labs(
    title = "Healthy life expectancy, (upper age band 85+)",
    subtitle = 'by ICS/MSOA',
    fill = 'Healthy life\nexpectancy (years)'
  ) +
  theme_dark(base_size = 12) +
  theme(
    legend.position = c(.2, .5),
    axis.text = element_blank(),
    axis.title = element_blank()
  )

ggsave(plot = msoa_map, path = 'output/maps', filename = 'msoa_map.png', 
       height = 3, width = 2.5, scale = 3, bg = colours[['dark']])

msoa_map

disp <-
  msoa_data %>%
  filter(ind_name =="Healthy life expectancy, (upper age band 85+)", is.finite(value)) %>%
  mutate(lab = if_else(value < 48, msoa_name, '')) %>%
  left_join(select(ics, stp19cd, long,lat) %>% as_tibble(), by = c('stp_code' = 'stp19cd')) %>%
  ggplot(aes(x = fct_reorder(stp_name, lat), y = value, label = lab)) +
  geom_point(aes(colour = value), alpha = .3, size = 1) +
  geom_boxplot(fill = 'transparent', width = .5, colour = '#ffffff80', outlier.shape = NA) +
  scale_colour_viridis_c(direction = -1, option = 'D') +
  coord_flip() +
  theme_dark() +
  theme(
    panel.grid.major.x = element_line(colour = paste0(colours[['light']], 50), size =.25),
    legend.position = 'none',
    axis.title = element_blank()
  )
  
disp

dual <-
  cowplot::plot_grid(msoa_map, disp, ncol = 2, rel_widths = c(1.4,1))

ggsave(plot = dual, path = 'output/maps', filename = 'map_boxplot.png', height = 3, width = 4, scale = 3, bg = colours[['dark']], dpi = 320)

dual

Plotting with labels

To help with interpretation of the map, it would be convineint to know the names of the places which

# fetch the tiles we need by using the eng object's bouding box with a small buffer.
# zoom level of 9 worked pretty appropriately
zl <- 8

tiles <- 
  eng %>%
  st_buffer(.2) %>%
  st_bbox() %>%
  as.vector() %>%
  get_stamenmap(zoom = zl, maptype = 'toner-labels')
st_buffer does not correctly buffer longitude/latitude data
ggmap(tiles)

You would think this would be easy but it doesn’t take long before the world of CRS projections comes to haunt you. Stamen map tiles are in projection EPSG:3857 but the bounding box used in ggmap is in EPSG:4326 and this makes everything look weird. It’s kind of odd as the ‘ggmap’ package itself fetches the tiles, and happily plots things until you start using other geospatial data at which point everything falls over. I have no idea how this all works to be honest, but this answer’s it better than I ever could. If we transform the SimpleFeatures we made previously to 3857 and then overlay them on the ggmap we can get some labels.

NB: There’s also an issue with the topology in the STP geojson file from the ONS which causes sf to throw an error saying points intersect when plotting. This is probably as a result of me getting the most generalised boundaries availiable to make things quicker. You can get around this by using st_buffer with a distance (dist) of 0. This seems to be some kind of catch-all remedy for troublesome geospatial data. It’s beyond my ability to explain why it works though.

# transform the objects we created alredy to EPSG:3857
eng_punch_3857 <- st_transform(eng_punch, 3857)
ics_3857 <- 
  # project to CRS used in stamen maps
  st_transform(ics, 3857) %>% 
  # back to an sf object
  st_as_sf() %>%
  # this is a hack to get around topology errors
  st_buffer(dist = 0)

msoa_data_3857 <- 
  msoa_data %>%
  filter(ind_name =="Healthy life expectancy, (upper age band 85+)") %>%
  st_transform(3857) %>%
  st_buffer(dist = 0)

# transform bounding box to work correctly because of an issue with
# how ggmap stores bounding box information.
attr(tiles, "bb")$ll.lat <- st_bbox(eng_punch_3857)["ymin"]
attr(tiles, "bb")$ll.lon <- st_bbox(eng_punch_3857)["xmin"]
attr(tiles, "bb")$ur.lat <- st_bbox(eng_punch_3857)["ymax"]
attr(tiles, "bb")$ur.lon <- st_bbox(eng_punch_3857)["xmax"]

Once we’ve messed about with CRS and forced everything into the new EPSG:3857 world we can put it all together! Order is important here as we’re essentailly layering the map up bit by bit.

I’ve gone with: 1. Load the base map This is the ggmap which has our stamen map labels which are essentially the background for everything else which sits on top. I’ve immediately followed this with set coordinates manually to a CRS of 3857, otherwise when we start plotting more things it’s going to try and use 4326 again. 2. Add a mask for England Slap on the ‘eng_punch’ object we created earlier to mask any bits of the stamen tiles outside of england giving a nice crisp edge. I’ve set this to be the same fill as the overall plot and an opacity of 100% (default). This takes out labels from Scotland, Wales, Northern Ireland and ROI which would otherwise just be floating in the distance. Sadly this does have the effect of clipping the edges of any labels that cross the coastline, but que sera sera… 3. Layer the MSOA data Here’s the fun bit, the filled geoms we made earlier now sit on top with an alpha of 50% to allow the base map labels to still be visible beneath. 4. Add ICS boundaries and labels To give the last bit of pertinent info about where ICS boundaries fall, layer on the ICS boundaries to as a thing outline with no fill. Drop labels in to display names. I’ve used a 20% alpha and a white fill to make the text stand out a little whilst trying not to lose too much detail out of the underlying map.

lab_map <-
  # start with tiles
  ggmap(tiles) +
  # force coordinate system to work properly
  coord_sf(crs = st_crs(3857)) +
  # add on the punch to clip around the coastline/border with Scotland
  geom_sf(data = eng_punch_3857, fill = colours[['dark']], colour = 'transparent', inherit.aes = FALSE) +
  # add the msoa level data as before but with a lower alpha this time
  geom_sf(data = msoa_data_3857,
          aes(fill = value),
          alpha = .65, colour = 'transparent', size = 0,
          inherit.aes = FALSE) +
  # # layer the ics outlines
  geom_sf(data = ics_3857,
          colour = colours[['light']], size = .25, fill = 'transparent',
          inherit.aes = FALSE) +
  # # plot on text to label ICS 
  geom_sf_text(data = ics_3857,
            aes(label = stp_name %>% str_wrap(25)),
            size = 1.5, colour = colours[['light']], inherit.aes = FALSE) +
  scale_fill_viridis_c(option = 'D', direction = -1) +
  labs(
    title = "Healthy life expectancy, (upper age band 85+)",
    subtitle = 'by ICS/MSOA',
    fill = 'Healthy life\nexpectancy (years)'
  ) +
  theme_dark(base_size = 12) +
  theme(
    legend.position = c(.2, .5),
    axis.text = element_blank(),
    axis.title = element_blank()
  )


ggsave(plot = lab_map, path = 'output/maps', filename = 'lab_map.png', 
       height = 3, width = 2.5, scale = 3, bg = colours[['dark']], dpi = 640)

lab_map

Putting this newly labelled map together with our boxplots again gives a good overall picture of what’s happening.

lab_map_box <-
  cowplot::plot_grid(lab_map, disp, ncol = 2, rel_widths = c(1.4, 1))
font family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font databasefont family 'Helvetica Neue Thin' not found in PostScript font database
ggsave(plot = lab_map_box, path = 'output', filename = 'labmap_box.png', height = 3, width = 4, scale = 3, bg = colours[['dark']], dpi = 320)

lab_map_box

LS0tCnRpdGxlOiAiTWFwcGluZyBIZWFsdGggT3V0Y29tZXMgZm9yIEludGVncmF0ZWQgQ2FyZSBTeXN0ZW0iCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCiMgSW50cm9kdWN0aW9uCgpgYGB7ciBzZXR1cCwgd2FybmluZyA9IEZBTFNFfQpwayA8LSBjKCd0aWR5dmVyc2UnLCAnc2YnLCAnZ2dyZXBlbCcsICdmaW5nZXJ0aXBzUicsICdnZ21hcCcsICdjb3dwbG90JykKc2FwcGx5KHBrLCBmdW5jdGlvbih4KXsKICBpZighKHggJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKSkpewogICAgaW5zdGFsbC5wYWNrYWdlcyh4LCBjaGFyYWN0ZXIub25seSA9IFRSVUUpCiAgfQogIHJlcXVpcmUoeCwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKQp9KQoKbGlzdC5maWxlcygnZXRjJywgZnVsbC5uYW1lcyA9IFRSVUUsIHBhdHRlcm4gPSAnLnInKSAlPiUKICBtYXAoc291cmNlKQoKaWYoIWRpci5leGlzdHMoJ21hcF9qc29uJykpe2Rpci5jcmVhdGUoJ21hcF9qc29uJyl9Cgpsb2FkX3NmIDwtIGZ1bmN0aW9uKHUsIHBhdGgsIGZpbGUpewogIGZwIDwtIHBhc3RlMChwYXRoLCAnLycsIGZpbGUsICcuZ2VvanNvbicpCiAgaWYoIWZpbGUuZXhpc3RzKGZwKSl7CiAgICBkb3dubG9hZC5maWxlKHVybCA9IHUsIGRlc3RmaWxlID0gZnApCiAgfQogIHJlYWRfc2YoZnApCn0KICAKaWNzIDwtICdodHRwczovL29wZW5kYXRhLmFyY2dpcy5jb20vZGF0YXNldHMvMWEzYjNlOWYwNDRhNDRhODk1ZDY0MDAwY2RkZjkyNzJfMC5nZW9qc29uJyAlPiUKICBsb2FkX3NmKHBhdGggPSAnbWFwX2pzb24nLCBmaWxlID0gJ2ljcycpICU+JQogIG11dGF0ZSgKICAgIHN0cF9uYW1lID0gCiAgICAgIHN0cl9yZW1vdmVfYWxsKHN0cmluZyA9IHN0cl90b19sb3dlcihzdHAxOW5tKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGF0dGVybiA9ICdoZWFsdGguKmNhcmUuKnxwYXJ0bmVycyBpbnxoZWFsdGhbYS16XXswLDN9fGpvaW4uKmNhcmV8b3VyfFxcKCcpICU+JQogICAgICBzdHJfc3F1aXNoKCkgJT4lCiAgICAgIHN0cl90b190aXRsZSgpICU+JQogICAgICBzdHJfcmVwbGFjZV9hbGwoYygnQW5kJz0nYW5kJywgJ1RoZScgPSAndGhlJywgJ09mJyA9ICdvZicpKQogICkKCidodHRwczovL29wZW5kYXRhLmFyY2dpcy5jb20vZGF0YXNldHMvYzY2MWE4Mzc3ZTI2NDdiMGJhZTY4YzQ5MTFkZjg2OGJfMy5nZW9qc29uJwptc29hIDwtICdodHRwczovL29wZW5kYXRhLmFyY2dpcy5jb20vZGF0YXNldHMvYzY2MWE4Mzc3ZTI2NDdiMGJhZTY4YzQ5MTFkZjg2OGJfMy5nZW9qc29uJyAlPiUKICBsb2FkX3NmKHBhdGggPSAnbWFwX2pzb24nLCBmaWxlID0gJ21zb2EnKQoKZW5nIDwtICdodHRwczovL29wZW5kYXRhLmFyY2dpcy5jb20vZGF0YXNldHMvNjI5YzMwM2UwN2VlNGFkMDlhNGRmZDBiZmVhNDk5ZWNfMC5nZW9qc29uJyAlPiUKICBsb2FkX3NmKHBhdGggPSAnbWFwX2pzb24nLCBmaWxlID0gJ2NvdW50cmllcycpICU+JSAKICBmaWx0ZXIoY3RyeTE4bm0gPT0gJ0VuZ2xhbmQnKQoKZW5nX3B1bmNoIDwtCiAgZW5nICU+JQogIHN0X2J1ZmZlcihkaXN0ID0gLjIpICU+JQogIHN0X2Jib3goKSAlPiUgCiAgc3RfbWFrZV9ncmlkKG4gPSAxKSAlPiUgCiAgc3RfZGlmZmVyZW5jZShlbmcpCmBgYAoKIyBQbG90dGluZyBJQ1NzCgpMaW5pbmcgdGhpbmdzIHVwIG5lYXRseSBpcyBhbHdheXMgYSBwbHVzLiBVc2luZyBhIGZpZyBhc3BlY3QgcnRpbyBvZiAxLjI1MzUgZ2V0cyByaWQgb2YgdGhlIHN0cmlwcyBhcm91bmQgdGhlIHNpemUgZm9yIHRoaXMgcHJvamVjdGlvbi4KCmBgYHtyIGJhc2ljX3Bsb3QsIGZpZy5oZWlnaHQgPSAxLjUsIGZpZy5hc3A9MS4yNX0KbSA8LQogIGdncGxvdChpY3MsIGFlcyh4ID0gbG9uZywgeSA9IGxhdCwgbGFiZWwgPSBzdHJfd3JhcChzdHAxOW5tLCAyNSksIGZpbGwgPSBzdF9hcmVhc2hhcGUpKSArCiAgZ2VvbV9zZihzaXplID0gLjEsIGNvbG91ciA9IGNvbG91cnNbWydkYXJrJ11dLCAgYWxwaGEgPSAuNSwgZmlsbCA9IGNvbG91cnNbWydsaWdodCddXSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkgKwogIGxhYnMoCiAgICB0aXRsZSA9ICdBIHNpbXBsZSBJQ1MgbWFwJwogICkgKwogIHRoZW1lX2RhcmsoKSArCiAgdGhlbWUoCiAgICBsZWdlbmQucG9zaXRpb24gPSBjKC4xLCAuNSksCiAgICBheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpCiAgKQoKaWYoIWRpci5leGlzdHMoJ291dHB1dC9tYXBzJykpe2Rpci5jcmVhdGUoJ291dHB1dC9tYXBzJywgcmVjdXJzaXZlID0gVFJVRSl9Cmdnc2F2ZShwbG90ID0gbSwgcGF0aCA9ICdvdXRwdXQvbWFwcycsIGZpbGVuYW1lID0gJ3NpbXBsZV9pY3MucG5nJywgaGVpZ2h0ID0gOCwgd2lkdGggPSA4LzEuMjUsIGJnID0gJyMyMjI2MjknKQoKbQpgYGAKCiMgR2V0dGluZyBzb21lIGRhdGEKSGFuZGlseSB0aGUgW2ZpbmdldGlwc1JdKGh0dHBzOi8vZ2l0aHViLmNvbS9yb3BlbnNjaS9maW5nZXJ0aXBzUikgcGFja2FnZSBmcm9tIFB1YmxpYyBIZWFsdGggRW5nbGFuZCBoYXMgc29tZSBncmVhdCBkYXRhIGF0IE1TT0EgbGV2ZWwgd2hpY2ggd29ya3MgcmVhbGx5IG5pY2VseSBvbiBtYXBzIHRvIGlsbHVzdHJhdGUgaGVhbHRoIGluZXF1YWxpdGllcy4gRG93bmxvYWQgYSBsb2NhbCBjb3B5IG9mIHRoZSBmaWxlIHRvIHNhdmUgaGFtbWVyaW5nIHRoZSBQSEUgc2VydmVycyEgVGhpcyB3aWxsIHRha2UgYSB3aGlsZSB0aGUgZmlyc3QgdGltZSBpdCdzIHJ1biBiZWNhdXNlIGl0IG5lZWRzIHRvIGZldGNoIGRhdGEgYW5kIHdyaXRlIGl0IGxvY2FsbHkgYnV0IHNob3VsZCBzcGVlZCB1cCBhZnRlciB0aGUgZG93bmxvYWRzIGZyb20gUEhFIGFyZSBkb25lLgoKYGBge3IgZ2V0X2ZpbmdlcnRpcHMsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQppZighZGlyLmV4aXN0cygnZGF0YScpKXtkaXIuY3JlYXRlKCdkYXRhJyl9CgojIGdldCBmaW5nZXJ0aXBzIGRhdGEKaWYoIWZpbGUuZXhpc3RzKCdkYXRhL2ZpbmdlcnRpcHNfZGF0YS5jc3YnKSl7CiAgZmluZ2VydGlwc1I6OmluZGljYXRvcnMoKSAlPiUKICBmaWx0ZXIoc3RyX2RldGVjdCh0b2xvd2VyKEluZGljYXRvck5hbWUpLCAnaGVhbHRoeSBsaWZlIGV4cHxtb3J0YWxpdHknKSkgJT4lCiAgcGx1Y2soJ0luZGljYXRvcklEJykgJT4lCiAgdW5pcXVlKCkgJT4lCiAgZmluZ2VydGlwc19kYXRhKEFyZWFUeXBlSUQgPSAzKSAlPiUKICB3cml0ZS5jc3YoJ2RhdGEvZmluZ2VydGlwc19kYXRhLmNzdicsIG5hID0gJycpCn0KCiMgZ2V0IGxzb2EgbG9va3VwCmlmKCFmaWxlLmV4aXN0cygnZGF0YS9sc29hX3N0cC5jc3YnKSl7CiAgJ2h0dHBzOi8vb3BlbmRhdGEuYXJjZ2lzLmNvbS9kYXRhc2V0cy81MjBlOWNkMjk0Yzg0ZGZhYWY5N2NjOTE0OTQyMzdhY18wLmNzdicgJT4lCiAgICBkb3dubG9hZC5maWxlKCdkYXRhL2xzb2Ffc3RwLmNzdicsIG1vZGUgPSAndycpCn0KCiMgZ2V0IG9hIGxvb2t1cAppZighZmlsZS5leGlzdHMoJ2RhdGEvb2FfbG9va3VwLmNzdicpKXsKICAnaHR0cDovL2dlb3BvcnRhbDEtb25zLm9wZW5kYXRhLmFyY2dpcy5jb20vZGF0YXNldHMvZmU2YzU1ZjA5MjRiNDczNGFkZjFjZjcxMDRhMDE3M2VfMC5jc3YnICU+JQogICAgZG93bmxvYWQuZmlsZSgnZGF0YS9vYV9sb29rdXAuY3N2JywgbW9kZSA9ICd3JykKfQoKZGF0YSA8LSByZWFkX2NzdignZGF0YS9maW5nZXJ0aXBzX2RhdGEuY3N2JykKbHNvYV9zdHAgPC1yZWFkX2NzdignZGF0YS9sc29hX3N0cC5jc3YnKQpvYV9sb29rdXAgPC0KICByZWFkX2NzdignZGF0YS9vYV9sb29rdXAuY3N2JykgJT4lCiAgZ3JvdXBfYnkoTFNPQTExQ0QsIExTT0ExMU5NLCBNU09BMTFDRCwgTVNPQTExTk0pICU+JQogIHN1bW1hcmlzZSgpICU+JQogIGxlZnRfam9pbihsc29hX3N0cCwgYnkgPSAnTFNPQTExQ0QnKSAlPiUKICBncm91cF9ieShNU09BMTFDRCwgTVNPQTExTk0sIExBRDE5Q0QsIExBRDE5Tk0sIENDRzE5Q0QsIENDRzE5Tk0sIFNUUDE5Q0QsIFNUUDE5Tk0pICU+JQogIHN1bW1hcmlzZSgpICU+JQogIHVuZ3JvdXAoKQpgYGAKCmBgYHtyIG1zb2FfZGF0YX0KbXNvYV9kYXRhIDwtCiAgZGF0YSAlPiUKICBhc190aWJibGUoKSAlPiUKICBmaWx0ZXIoQXJlYVR5cGUgPT0gIk1TT0EiKSAlPiUKICB0cmFuc211dGUoCiAgICBpbmRfaWQgPSBJbmRpY2F0b3JJRCwKICAgIGluZF9uYW1lID0gSW5kaWNhdG9yTmFtZSwKICAgIG1zb2FfY29kZSA9IEFyZWFDb2RlLAogICAgdmFsdWUgPSBWYWx1ZSwKICAgIGNvdW50ID0gQ291bnQsCiAgICBkbm0gPSBEZW5vbWluYXRvcgogICkgJT4lCiAgbGVmdF9qb2luKHkgPSAKICAgICAgICAgICAgICBvYV9sb29rdXAgJT4lCiAgICAgICAgICAgICAgdHJhbnNtdXRlKAogICAgICAgICAgICAgICAgbXNvYV9jb2RlID0gTVNPQTExQ0QsCiAgICAgICAgICAgICAgICBsYWRfY29kZSA9IExBRDE5Q0QsCiAgICAgICAgICAgICAgICBsYWRfbmFtZSA9IExBRDE5Tk0sCiAgICAgICAgICAgICAgICBjY2dfY29kZSA9IENDRzE5Q0QsCiAgICAgICAgICAgICAgICBjY2dfbmFtZSA9IENDRzE5Tk0sCiAgICAgICAgICAgICAgICBzdHBfY29kZSA9IFNUUDE5Q0QsCiAgICAgICAgICAgICAgICBzdHBfbmFtZSA9IFNUUDE5Tk0KICAgICAgICAgICAgICApLAogICAgICAgICAgICBieSA9ICdtc29hX2NvZGUnCiAgICAgICAgICAgICkgJT4lCiAgbGVmdF9qb2luKHkgPSAKICAgICAgICAgICAgICBtc29hICU+JQogICAgICAgICAgICAgIHRyYW5zbXV0ZSgKICAgICAgICAgICAgICAgIG9iamVjdGlkaWQgPSBvYmplY3RpZCwKICAgICAgICAgICAgICAgIG1zb2FfY29kZSA9IG1zb2ExMWNkLAogICAgICAgICAgICAgICAgbXNvYV9uYW1lID0gbXNvYTExbm0sCiAgICAgICAgICAgICAgICBnZW9tZXRyeSA9IGdlb21ldHJ5CiAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgYnkgPSAnbXNvYV9jb2RlJwogICkgJT4lCiAgc3RfYXNfc2YoKQoKIyBjbGVhbiB1cApuZWVkIDwtIGMoJ21zb2FfZGF0YScsICdpY3MnLCAnZW5nJywgJ2VuZ19wdW5jaCcsICd0aGVtZV9kYXJrJywgJ3RoZW1lX2xpZ2h0JywgJ2NvbG91cnMnKQpybXYgPC0gYygncm12JywgbHMoKVshKGxzKCkgJWluJSBuZWVkKV0pCnJtKGxpc3QgPSBybXYpCmBgYAoKYGBge3IgbXNvYV9tYXAsIGZpZy5oZWlnaHQgPSAxLjUsIGZpZy5hc3A9MS4zfQptc29hX21hcCA8LSAKICBtc29hX2RhdGEgJT4lIAogIGZpbHRlcihpbmRfbmFtZSA9PSJIZWFsdGh5IGxpZmUgZXhwZWN0YW5jeSwgKHVwcGVyIGFnZSBiYW5kIDg1KykiKSAlPiUKICBnZ3Bsb3QoYWVzKGZpbGwgPSB2YWx1ZSkpICsKICBnZW9tX3NmKGNvbG91ciA9ICd0cmFuc3BhcmVudCcsIHNpemUgPSAwLCBhbHBoYSA9LjcpICsKICBnZW9tX3NmKGRhdGEgPSBpY3MsIGNvbG91ciA9IGNvbG91cnNbWydsaWdodCddXSwgc2l6ZSA9IC4yNSwgZmlsbCA9ICd0cmFuc3BhcmVudCcsIGluaGVyaXQuYWVzID0gRkFMU0UpICsKICBnZW9tX3RleHQoZGF0YSA9IGljcywgCiAgICAgICAgICAgIGFlcyh4ID0gbG9uZywgeSA9IGxhdCwgbGFiZWwgPSBzdHBfbmFtZSAlPiUgc3RyX3dyYXAoMjUpKSwgCiAgICAgICAgICAgIHNpemUgPSAxLjUsIGNvbG91ciA9IGNvbG91cnNbWydsaWdodCddXSwgaW5oZXJpdC5hZXMgPSBGQUxTRSkgKyAKICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhvcHRpb24gPSAnRCcsIGRpcmVjdGlvbiA9IC0xKSArCiAgbGFicygKICAgIHRpdGxlID0gIkhlYWx0aHkgbGlmZSBleHBlY3RhbmN5LCAodXBwZXIgYWdlIGJhbmQgODUrKSIsCiAgICBzdWJ0aXRsZSA9ICdieSBJQ1MvTVNPQScsCiAgICBmaWxsID0gJ0hlYWx0aHkgbGlmZVxuZXhwZWN0YW5jeSAoeWVhcnMpJwogICkgKwogIHRoZW1lX2RhcmsoYmFzZV9zaXplID0gMTIpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9IGMoLjIsIC41KSwKICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkKICApCgpnZ3NhdmUocGxvdCA9IG1zb2FfbWFwLCBwYXRoID0gJ291dHB1dC9tYXBzJywgZmlsZW5hbWUgPSAnbXNvYV9tYXAucG5nJywgCiAgICAgICBoZWlnaHQgPSAzLCB3aWR0aCA9IDIuNSwgc2NhbGUgPSAzLCBiZyA9IGNvbG91cnNbWydkYXJrJ11dKQoKbXNvYV9tYXAKYGBgCgpgYGB7ciBib3hwbG90LCBmaWcuaGVpZ2h0PTEuNSwgZmlnLmFzcD0xLjV9CmRpc3AgPC0KICBtc29hX2RhdGEgJT4lCiAgZmlsdGVyKGluZF9uYW1lID09IkhlYWx0aHkgbGlmZSBleHBlY3RhbmN5LCAodXBwZXIgYWdlIGJhbmQgODUrKSIsIGlzLmZpbml0ZSh2YWx1ZSkpICU+JQogIG11dGF0ZShsYWIgPSBpZl9lbHNlKHZhbHVlIDwgNDgsIG1zb2FfbmFtZSwgJycpKSAlPiUKICBsZWZ0X2pvaW4oc2VsZWN0KGljcywgc3RwMTljZCwgbG9uZyxsYXQpICU+JSBhc190aWJibGUoKSwgYnkgPSBjKCdzdHBfY29kZScgPSAnc3RwMTljZCcpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBmY3RfcmVvcmRlcihzdHBfbmFtZSwgbGF0KSwgeSA9IHZhbHVlLCBsYWJlbCA9IGxhYikpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvdXIgPSB2YWx1ZSksIGFscGhhID0gLjMsIHNpemUgPSAxKSArCiAgZ2VvbV9ib3hwbG90KGZpbGwgPSAndHJhbnNwYXJlbnQnLCB3aWR0aCA9IC41LCBjb2xvdXIgPSAnI2ZmZmZmZjgwJywgb3V0bGllci5zaGFwZSA9IE5BKSArCiAgc2NhbGVfY29sb3VyX3ZpcmlkaXNfYyhkaXJlY3Rpb24gPSAtMSwgb3B0aW9uID0gJ0QnKSArCiAgY29vcmRfZmxpcCgpICsKICB0aGVtZV9kYXJrKCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9saW5lKGNvbG91ciA9IHBhc3RlMChjb2xvdXJzW1snbGlnaHQnXV0sIDUwKSwgc2l6ZSA9LjI1KSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICdub25lJywKICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkKICApCiAgCmRpc3AKYGBgCgoKYGBge3IgbWFwX2FuZF9ib3gsIGZpZy5oZWlnaHQgPSAxLjUsIGZpZy5hc3AgPSAuNzUsIHdhcm5pbmcgPSBGQUxTRX0KZHVhbCA8LSBwbG90X2dyaWQobXNvYV9tYXAsIGRpc3AsIG5jb2wgPSAyLCByZWxfd2lkdGhzID0gYygxLjQsMSkpCgpnZ3NhdmUocGxvdCA9IGR1YWwsIHBhdGggPSAnb3V0cHV0L21hcHMnLCBmaWxlbmFtZSA9ICdtYXBfYm94cGxvdC5wbmcnLCBoZWlnaHQgPSAzLCB3aWR0aCA9IDQsIHNjYWxlID0gMywgYmcgPSBjb2xvdXJzW1snZGFyayddXSwgZHBpID0gMzIwKQoKZHVhbApgYGAKCiMgUGxvdHRpbmcgd2l0aCBsYWJlbHMKClRvIGhlbHAgd2l0aCBpbnRlcnByZXRhdGlvbiBvZiB0aGUgbWFwLCBpdCB3b3VsZCBiZSBjb252aW5laW50IHRvIGtub3cgdGhlIG5hbWVzIG9mIHRoZSBwbGFjZXMgd2hpY2ggCgpgYGB7ciBnZXRfbGFiZWxzLCBmaWcuaGVpZ2h0ID0gMiwgZmlnLmFzcCA9IDEuMjUsIG1lc3NhZ2UgPSBGQUxTRX0KIyBmZXRjaCB0aGUgdGlsZXMgd2UgbmVlZCBieSB1c2luZyB0aGUgZW5nIG9iamVjdCdzIGJvdWRpbmcgYm94IHdpdGggYSBzbWFsbCBidWZmZXIuCiMgem9vbSBsZXZlbCBvZiA5IHdvcmtlZCBwcmV0dHkgYXBwcm9wcmlhdGVseQp6bCA8LSA4Cgp0aWxlcyA8LSAKICBlbmcgJT4lCiAgc3RfYnVmZmVyKC4yKSAlPiUKICBzdF9iYm94KCkgJT4lCiAgYXMudmVjdG9yKCkgJT4lCiAgZ2V0X3N0YW1lbm1hcCh6b29tID0gemwsIG1hcHR5cGUgPSAndG9uZXItbGFiZWxzJykKCmdnbWFwKHRpbGVzKQpgYGAKCllvdSB3b3VsZCB0aGluayB0aGlzIHdvdWxkIGJlIGVhc3kgYnV0IGl0IGRvZXNuJ3QgdGFrZSBsb25nIGJlZm9yZSB0aGUgd29ybGQgb2YgQ1JTIHByb2plY3Rpb25zIGNvbWVzIHRvIGhhdW50IHlvdS4gU3RhbWVuIG1hcCB0aWxlcyBhcmUgaW4gcHJvamVjdGlvbiBFUFNHOjM4NTcgYnV0IHRoZSBib3VuZGluZyBib3ggdXNlZCBpbiBnZ21hcCBpcyBpbiBFUFNHOjQzMjYgYW5kIHRoaXMgbWFrZXMgZXZlcnl0aGluZyBsb29rIHdlaXJkLiBJdCdzIGtpbmQgb2Ygb2RkIGFzIHRoZSAnZ2dtYXAnIHBhY2thZ2UgaXRzZWxmIGZldGNoZXMgdGhlIHRpbGVzLCBhbmQgaGFwcGlseSBwbG90cyB0aGluZ3MgdW50aWwgeW91IHN0YXJ0IHVzaW5nIF9vdGhlcl8gZ2Vvc3BhdGlhbCBkYXRhIGF0IHdoaWNoIHBvaW50IGV2ZXJ5dGhpbmcgZmFsbHMgb3Zlci4gSSBoYXZlIG5vIGlkZWEgaG93IHRoaXMgYWxsIHdvcmtzIHRvIGJlIGhvbmVzdCwgYnV0IFt0aGlzIGFuc3dlcidzIGl0XShodHRwczovL2dpdGh1Yi5jb20vZGthaGxlL2dnbWFwL2lzc3Vlcy8xNjAgImdpdGh1YiBpc3N1ZSIpIGJldHRlciB0aGFuIEkgZXZlciBjb3VsZC4gSWYgd2UgdHJhbnNmb3JtIHRoZSBTaW1wbGVGZWF0dXJlcyB3ZSBtYWRlIHByZXZpb3VzbHkgdG8gMzg1NyBhbmQgdGhlbiBvdmVybGF5IHRoZW0gb24gdGhlIGdnbWFwIHdlIGNhbiBnZXQgc29tZSBsYWJlbHMuIAoKKk5COiogVGhlcmUncyBhbHNvIGFuIGlzc3VlIHdpdGggdGhlIHRvcG9sb2d5IGluIHRoZSBTVFAgZ2VvanNvbiBmaWxlIGZyb20gdGhlIE9OUyB3aGljaCBjYXVzZXMgc2YgdG8gdGhyb3cgYW4gZXJyb3Igc2F5aW5nIHBvaW50cyBpbnRlcnNlY3Qgd2hlbiBwbG90dGluZy4gVGhpcyBpcyBwcm9iYWJseSBhcyBhIHJlc3VsdCBvZiBtZSBnZXR0aW5nIHRoZSBtb3N0IGdlbmVyYWxpc2VkIGJvdW5kYXJpZXMgYXZhaWxpYWJsZSB0byBtYWtlIHRoaW5ncyBxdWlja2VyLiBZb3UgY2FuIGdldCBhcm91bmQgdGhpcyBieSB1c2luZyBzdF9idWZmZXIgd2l0aCBhIGRpc3RhbmNlIChkaXN0KSBvZiAwLiBUaGlzIHNlZW1zIHRvIGJlIHNvbWUga2luZCBvZiBjYXRjaC1hbGwgcmVtZWR5IGZvciB0cm91Ymxlc29tZSBnZW9zcGF0aWFsIGRhdGEuIEl0J3MgYmV5b25kIG15IGFiaWxpdHkgdG8gZXhwbGFpbiBfd2h5XyBpdCB3b3JrcyB0aG91Z2guCgoKYGBge3IgY2hhbmdlX2Nyc30KIyB0cmFuc2Zvcm0gdGhlIG9iamVjdHMgd2UgY3JlYXRlZCBhbHJlZHkgdG8gRVBTRzozODU3CmVuZ19wdW5jaF8zODU3IDwtIHN0X3RyYW5zZm9ybShlbmdfcHVuY2gsIDM4NTcpCmljc18zODU3IDwtIAogICMgcHJvamVjdCB0byBDUlMgdXNlZCBpbiBzdGFtZW4gbWFwcwogIHN0X3RyYW5zZm9ybShpY3MsIDM4NTcpICU+JSAKICAjIGJhY2sgdG8gYW4gc2Ygb2JqZWN0CiAgc3RfYXNfc2YoKSAlPiUKICAjIHRoaXMgaXMgYSBoYWNrIHRvIGdldCBhcm91bmQgdG9wb2xvZ3kgZXJyb3JzCiAgc3RfYnVmZmVyKGRpc3QgPSAwKQoKbXNvYV9kYXRhXzM4NTcgPC0gCiAgbXNvYV9kYXRhICU+JQogIGZpbHRlcihpbmRfbmFtZSA9PSJIZWFsdGh5IGxpZmUgZXhwZWN0YW5jeSwgKHVwcGVyIGFnZSBiYW5kIDg1KykiKSAlPiUKICBzdF90cmFuc2Zvcm0oMzg1NykgJT4lCiAgc3RfYnVmZmVyKGRpc3QgPSAwKQoKIyB0cmFuc2Zvcm0gYm91bmRpbmcgYm94IHRvIHdvcmsgY29ycmVjdGx5IGJlY2F1c2Ugb2YgYW4gaXNzdWUgd2l0aAojIGhvdyBnZ21hcCBzdG9yZXMgYm91bmRpbmcgYm94IGluZm9ybWF0aW9uLgphdHRyKHRpbGVzLCAiYmIiKSRsbC5sYXQgPC0gc3RfYmJveChlbmdfcHVuY2hfMzg1NylbInltaW4iXQphdHRyKHRpbGVzLCAiYmIiKSRsbC5sb24gPC0gc3RfYmJveChlbmdfcHVuY2hfMzg1NylbInhtaW4iXQphdHRyKHRpbGVzLCAiYmIiKSR1ci5sYXQgPC0gc3RfYmJveChlbmdfcHVuY2hfMzg1NylbInltYXgiXQphdHRyKHRpbGVzLCAiYmIiKSR1ci5sb24gPC0gc3RfYmJveChlbmdfcHVuY2hfMzg1NylbInhtYXgiXQpgYGAKCk9uY2Ugd2UndmUgbWVzc2VkIGFib3V0IHdpdGggQ1JTIGFuZCBmb3JjZWQgZXZlcnl0aGluZyBpbnRvIHRoZSBuZXcgRVBTRzozODU3IHdvcmxkIHdlIGNhbiBwdXQgaXQgYWxsIHRvZ2V0aGVyISBPcmRlciBpcyBpbXBvcnRhbnQgaGVyZSBhcyB3ZSdyZSBlc3NlbnRhaWxseSBsYXllcmluZyB0aGUgbWFwIHVwIGJpdCBieSBiaXQuCgpJJ3ZlIGdvbmUgd2l0aDoKICAxLiAqTG9hZCB0aGUgYmFzZSBtYXAqCiAgVGhpcyBpcyB0aGUgZ2dtYXAgd2hpY2ggaGFzIG91ciBzdGFtZW4gbWFwIGxhYmVscyB3aGljaCBhcmUgZXNzZW50aWFsbHkgdGhlIGJhY2tncm91bmQgZm9yIGV2ZXJ5dGhpbmcgZWxzZSB3aGljaCBzaXRzIG9uIHRvcC4gSSd2ZSBpbW1lZGlhdGVseSBmb2xsb3dlZCB0aGlzIHdpdGggc2V0IGNvb3JkaW5hdGVzIG1hbnVhbGx5IHRvIGEgQ1JTIG9mIDM4NTcsIG90aGVyd2lzZSB3aGVuIHdlIHN0YXJ0IHBsb3R0aW5nIG1vcmUgdGhpbmdzIGl0J3MgZ29pbmcgdG8gdHJ5IGFuZCB1c2UgNDMyNiBhZ2Fpbi4KICAyLiAqQWRkIGEgbWFzayBmb3IgRW5nbGFuZCoKICBTbGFwIG9uIHRoZSAnZW5nX3B1bmNoJyBvYmplY3Qgd2UgY3JlYXRlZCBlYXJsaWVyIHRvIG1hc2sgYW55IGJpdHMgb2YgdGhlIHN0YW1lbiB0aWxlcyBvdXRzaWRlIG9mIGVuZ2xhbmQgZ2l2aW5nIGEgbmljZSBjcmlzcCBlZGdlLiBJJ3ZlIHNldCB0aGlzIHRvIGJlIHRoZSBzYW1lIGZpbGwgYXMgdGhlIG92ZXJhbGwgcGxvdCBhbmQgYW4gb3BhY2l0eSBvZiAxMDAlIChkZWZhdWx0KS4gVGhpcyB0YWtlcyBvdXQgbGFiZWxzIGZyb20gU2NvdGxhbmQsIFdhbGVzLCBOb3J0aGVybiBJcmVsYW5kIGFuZCBST0kgd2hpY2ggd291bGQgb3RoZXJ3aXNlIGp1c3QgYmUgZmxvYXRpbmcgaW4gdGhlIGRpc3RhbmNlLiBTYWRseSB0aGlzIGRvZXMgaGF2ZSB0aGUgZWZmZWN0IG9mIGNsaXBwaW5nIHRoZSBlZGdlcyBvZiBhbnkgbGFiZWxzIHRoYXQgY3Jvc3MgdGhlIGNvYXN0bGluZSwgYnV0IHF1ZSBzZXJhIHNlcmEuLi4KICAzLiAqTGF5ZXIgdGhlIE1TT0EgZGF0YSoKICBIZXJlJ3MgdGhlIGZ1biBiaXQsIHRoZSBmaWxsZWQgZ2VvbXMgd2UgbWFkZSBlYXJsaWVyIG5vdyBzaXQgb24gdG9wIHdpdGggYW4gYWxwaGEgb2YgNTAlIHRvIGFsbG93IHRoZSBiYXNlIG1hcCBsYWJlbHMgdG8gc3RpbGwgYmUgdmlzaWJsZSBiZW5lYXRoLgogIDQuICpBZGQgSUNTIGJvdW5kYXJpZXMgYW5kIGxhYmVscyoKICBUbyBnaXZlIHRoZSBsYXN0IGJpdCBvZiBwZXJ0aW5lbnQgaW5mbyBhYm91dCB3aGVyZSBJQ1MgYm91bmRhcmllcyBmYWxsLCBsYXllciBvbiB0aGUgSUNTIGJvdW5kYXJpZXMgdG8gYXMgYSB0aGluZyBvdXRsaW5lIHdpdGggbm8gZmlsbC4gRHJvcCBsYWJlbHMgaW4gdG8gZGlzcGxheSBuYW1lcy4gSSd2ZSB1c2VkIGEgMjAlIGFscGhhIGFuZCBhIHdoaXRlIGZpbGwgdG8gbWFrZSB0aGUgdGV4dCBzdGFuZCBvdXQgYSBsaXR0bGUgd2hpbHN0IHRyeWluZyBub3QgdG8gbG9zZSB0b28gbXVjaCBkZXRhaWwgb3V0IG9mIHRoZSB1bmRlcmx5aW5nIG1hcC4KCmBgYHtyIGxhYmVsX21hcCwgZmlnLmhlaWdodD0zLCBmaWcuYXNwPTEuMywgbWVzc2FnZT1GQUxTRX0KbGFiX21hcCA8LQogICMgc3RhcnQgd2l0aCB0aWxlcwogIGdnbWFwKHRpbGVzKSArCiAgIyBmb3JjZSBjb29yZGluYXRlIHN5c3RlbSB0byB3b3JrIHByb3Blcmx5CiAgY29vcmRfc2YoY3JzID0gc3RfY3JzKDM4NTcpKSArCiAgIyBhZGQgb24gdGhlIHB1bmNoIHRvIGNsaXAgYXJvdW5kIHRoZSBjb2FzdGxpbmUvYm9yZGVyIHdpdGggU2NvdGxhbmQKICBnZW9tX3NmKGRhdGEgPSBlbmdfcHVuY2hfMzg1NywgZmlsbCA9IGNvbG91cnNbWydkYXJrJ11dLCBjb2xvdXIgPSAndHJhbnNwYXJlbnQnLCBpbmhlcml0LmFlcyA9IEZBTFNFKSArCiAgIyBhZGQgdGhlIG1zb2EgbGV2ZWwgZGF0YSBhcyBiZWZvcmUgYnV0IHdpdGggYSBsb3dlciBhbHBoYSB0aGlzIHRpbWUKICBnZW9tX3NmKGRhdGEgPSBtc29hX2RhdGFfMzg1NywKICAgICAgICAgIGFlcyhmaWxsID0gdmFsdWUpLAogICAgICAgICAgYWxwaGEgPSAuNjUsIGNvbG91ciA9ICd0cmFuc3BhcmVudCcsIHNpemUgPSAwLAogICAgICAgICAgaW5oZXJpdC5hZXMgPSBGQUxTRSkgKwogICMgIyBsYXllciB0aGUgaWNzIG91dGxpbmVzCiAgZ2VvbV9zZihkYXRhID0gaWNzXzM4NTcsCiAgICAgICAgICBjb2xvdXIgPSBjb2xvdXJzW1snbGlnaHQnXV0sIHNpemUgPSAuMjUsIGZpbGwgPSAndHJhbnNwYXJlbnQnLAogICAgICAgICAgaW5oZXJpdC5hZXMgPSBGQUxTRSkgKwogICMgIyBwbG90IG9uIHRleHQgdG8gbGFiZWwgSUNTIAogIGdlb21fc2ZfdGV4dChkYXRhID0gaWNzXzM4NTcsCiAgICAgICAgICAgIGFlcyhsYWJlbCA9IHN0cF9uYW1lICU+JSBzdHJfd3JhcCgyNSkpLAogICAgICAgICAgICBzaXplID0gMS41LCBjb2xvdXIgPSBjb2xvdXJzW1snbGlnaHQnXV0sIGluaGVyaXQuYWVzID0gRkFMU0UpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhvcHRpb24gPSAnRCcsIGRpcmVjdGlvbiA9IC0xKSArCiAgbGFicygKICAgIHRpdGxlID0gIkhlYWx0aHkgbGlmZSBleHBlY3RhbmN5LCAodXBwZXIgYWdlIGJhbmQgODUrKSIsCiAgICBzdWJ0aXRsZSA9ICdieSBJQ1MvTVNPQScsCiAgICBmaWxsID0gJ0hlYWx0aHkgbGlmZVxuZXhwZWN0YW5jeSAoeWVhcnMpJwogICkgKwogIHRoZW1lX2RhcmsoYmFzZV9zaXplID0gMTIpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9IGMoLjIsIC41KSwKICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkKICApCgoKZ2dzYXZlKHBsb3QgPSBsYWJfbWFwLCBwYXRoID0gJ291dHB1dC9tYXBzJywgZmlsZW5hbWUgPSAnbGFiX21hcC5wbmcnLCAKICAgICAgIGhlaWdodCA9IDMsIHdpZHRoID0gMi41LCBzY2FsZSA9IDMsIGJnID0gY29sb3Vyc1tbJ2RhcmsnXV0sIGRwaSA9IDY0MCkKCmxhYl9tYXAKYGBgCgoKUHV0dGluZyB0aGlzIG5ld2x5IGxhYmVsbGVkIG1hcCB0b2dldGhlciB3aXRoIG91ciBib3hwbG90cyBhZ2FpbiBnaXZlcyBhIGdvb2Qgb3ZlcmFsbCBwaWN0dXJlIG9mIHdoYXQncyBoYXBwZW5pbmcuCgpgYGB7ciBsYWJlbGxlZF9tYXBfYW5kX2JveHBsb3QsIGZpZy5oZWlnaHQ9MywgZmlnLmFzcD0uNzV9CmxhYl9tYXBfYm94IDwtIHBsb3RfZ3JpZChsYWJfbWFwLCBkaXNwLCBuY29sID0gMiwgcmVsX3dpZHRocyA9IGMoMS40LCAxKSkKCmdnc2F2ZShwbG90ID0gbGFiX21hcF9ib3gsIHBhdGggPSAnb3V0cHV0JywgZmlsZW5hbWUgPSAnbGFibWFwX2JveC5wbmcnLCBoZWlnaHQgPSAzLCB3aWR0aCA9IDQsIHNjYWxlID0gMywgYmcgPSBjb2xvdXJzW1snZGFyayddXSwgZHBpID0gMzIwKQoKbGFiX21hcF9ib3gKYGBg